﻿using ServiceOperater.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace ServiceOperater.Behaviors
{
    // 这里面有大量 is TextBox类似的判断 !!!
    // 可回滚
    public sealed class TransactionBehavior
    {
        // 控件 - 对应的DataContext 组成的键值对
        // 所以以Value反查key的话, 我们就能找到以该Value为DataContext的控件
        private static Dictionary<Control, Object> controlDataContextDic = new Dictionary<Control, object>();

        #region DP
        // 原始值
        private static DependencyProperty OriginValProperty = DependencyProperty.Register("OriginVal", typeof(Object), typeof(Control), new PropertyMetadata(String.Empty, (deo, args) =>
        {
            // DependencyObject d, DependencyPropertyChangedEventArgs e

        }));
        // 是否被编辑过?
        private static DependencyProperty IsEditProperty = DependencyProperty.Register("IsEdit", typeof(bool), typeof(Control), new PropertyMetadata(false, (deo, args) =>
        {
            // DependencyObject d, DependencyPropertyChangedEventArgs e

        }));
        #endregion

        static TransactionBehavior()
        {
            //
            // 应用
            App.Messenger.Register<Object>(MessengerToken.TRANSACTION_APPLY, (source) =>
            {
                foreach (KeyValuePair<Control, Object> keyVal in controlDataContextDic)
                {
                    // 更新每个控件自身记录的OriginVal
                    if (keyVal.Value == source)
                    {
                        var currentControl = keyVal.Key;
                        DependencyProperty dp = getDP(currentControl);
                        var name = currentControl.GetBindingExpression(dp).ParentBinding.Path.Path;
                        var val = source.GetType().GetProperty(name).GetValue(source, null);

                        currentControl.SetValue(OriginValProperty, val);
                        currentControl.Background = System.Windows.Media.Brushes.White;
                    }
                }

                checkIfItemOfDataContextChanged(source);
            });

            // 回滚
            App.Messenger.Register<Object>(MessengerToken.TRANSACTION_ROLLBACK, (source) =>
            {
                foreach (KeyValuePair<Control, Object> keyVal in controlDataContextDic)
                {
                    // 回滚每个控件自身记录的OriginVal
                    if (keyVal.Value == source)
                    {
                        var currentControl = keyVal.Key;
                        setControlValue(currentControl, currentControl.GetValue(OriginValProperty));
                    }
                }

                checkIfItemOfDataContextChanged(source);
            });

            // 检索原始值
            App.Messenger.Register<Object>(MessengerToken.TRANSACTION_RETRIEVAL, (source) =>
            {
                if (!(source is IFeedback))
                {
                    return;
                }

                var context = retrievalChanged(source);
                ((IFeedback)source).RetrievalChange(context, source);
            });
        }

        public static bool GetCanRollback(DependencyObject obj)
        {
            return (bool)obj.GetValue(CanRollbackProperty);
        }

        public static void SetCanRollback(DependencyObject obj, bool value)
        {
            obj.SetValue(CanRollbackProperty, value);
        }

        // Using a DependencyProperty as the backing store for CanRollback.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanRollbackProperty =
            DependencyProperty.RegisterAttached("CanRollback", typeof(bool), typeof(TransactionBehavior), new PropertyMetadata(false, OnValueChanged));


        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // 设置本控件最初始的值
            setOriginVal(d);

            // 挂接值改变时的事件
            onValChangedEvent(d);

            // 记录控件及其DataContext的对应关系
            controlDataContextDic.Add((Control)d, ((Control)d).DataContext);

            onDataContextChanged(d);

            //((SimpleDO)d).OnValueChanged(e);
        }

        private static void setOriginVal(DependencyObject d)
        {
            System.Windows.Data.BindingExpression be = ((Control)d).GetBindingExpression(getDP(((Control)d)));

            if (null == be)
            {
                return;
            }

            // 设置初始值
            var binding = be.ParentBinding;

            // source为 datacontext
            // binding.Source 这个Source就是binding里设置的哪个
            var source = be.DataItem;

            var path = binding.Path;
            var pi = source.GetType().GetProperty(path.Path);
            //if (null == pi)
            //{
            //    d.SetValue(OriginValProperty, "");
            //}
            //else
            //{
            // https://stackoverflow.com/questions/3531318/convert-changetype-fails-on-nullable-types
            var pType = pi.PropertyType;
            pType = Nullable.GetUnderlyingType(pType) ?? pType;
            var pVal = pi.GetValue(source, null);
            var value = pVal == null ? null : Convert.ChangeType(pVal, pType);
            d.SetValue(OriginValProperty, value);
            //}
            

        }

        private static void onValChangedEvent(DependencyObject d)
        {

            if (d is TextBox)
            {
                ((TextBox)d).TextChanged += (sender, args) =>
                {
                    // object sender, TextChangedEventArgs e
                    // var sou = args.Source; // 一般情况下等于sender
                    // 给出该控件内容已被修改
                    var tbCopy = ((TextBox)sender);
                    var changes = args.Changes;
                    var currentTxt = tbCopy.Text;

                    var originVal = ((TextBox)sender).GetValue(OriginValProperty);
                    if (originVal != null && currentTxt.Equals(originVal.ToString()))
                    {
                        tbCopy.SetValue(IsEditProperty, false);
                        tbCopy.Background = System.Windows.Media.Brushes.White;
                    }
                    else
                    {
                        tbCopy.SetValue(IsEditProperty, true);
                        tbCopy.Background = System.Windows.Media.Brushes.Red;
                    }

                    checkIfItemOfDataContextChanged(tbCopy.DataContext);
                };
            }
            else if (d is ComboBox)
            {
                ((ComboBox)d).SelectionChanged += (sender, args) =>
                {
                    // object sender, SelectionChangedEventArgs e
                    // var sou = args.Source; // 一般情况下等于sender
                    // 提示被修改
                    //var changes = args.
                    var cbCopy = ((ComboBox)sender);
                    var currentVal = cbCopy.SelectedItem;//SelectedValue取到的还是之前的值,而不是被回滚操作设置的值

                    var originVal = cbCopy.GetValue(OriginValProperty);
                    if (originVal != null && currentVal != null && currentVal.ToString().Equals(originVal.ToString()))
                    {
                        cbCopy.SetValue(IsEditProperty, false);
                        cbCopy.Background = System.Windows.Media.Brushes.White;
                    }
                    else
                    {
                        cbCopy.SetValue(IsEditProperty, true);
                        cbCopy.Background = System.Windows.Media.Brushes.Red;
                    }

                    checkIfItemOfDataContextChanged(cbCopy.DataContext);
                };
            }
        }

        private static void onDataContextChanged(DependencyObject d)
        {
            ((Control)d).DataContextChanged += (sender, args) =>
            {
                // object sender, DependencyPropertyChangedEventArgs e

                // 确认之前的记录正确
                if (controlDataContextDic[(Control)sender] != args.OldValue)
                {
                    throw new ArgumentException("之前的DataContext记录混乱！记录的为: [ " + controlDataContextDic[(Control)sender] + "]; 事件回调传递的是: [ " + args.OldValue + " ]");
                }
                //更新为 控件更换的DataContext
                controlDataContextDic.Add((Control)sender, args.NewValue);

                // ------ 修改OriginVal
                System.Windows.Data.BindingExpression beCopy = ((Control)d).GetBindingExpression(getDP(((Control)d)));

                if (null == beCopy)
                {
                    return;
                }

                var bindingCopy = beCopy.ParentBinding;
                var pathCopy = bindingCopy.Path;
                var piCopy = args.NewValue.GetType().GetProperty(pathCopy.Path);
                var valueCopy = piCopy.GetValue(args.NewValue, null);
                ((Control)sender).SetValue(OriginValProperty, valueCopy);
            };

        }

        private static void setControlValue(Control currentControl, Object val)
        {
            DependencyProperty dp = getDP(currentControl);
            currentControl.SetValue(dp, val);

        }

        private static void checkIfItemOfDataContextChanged(Object dataContext)
        {
            if (!(dataContext is IFeedback))
            {
                return;
            }

            var context = retrievalChanged(dataContext);

            ((IFeedback)dataContext).Feedback(context, dataContext);
        }

        private static Dictionary<String, Tuple<Object, Object>> retrievalChanged(Object dataContext)
        {

            Dictionary<String, Tuple<Object, Object>> context = new Dictionary<String, Tuple<Object, Object>>();
            foreach (KeyValuePair<Control, Object> keyVal in controlDataContextDic)
            {
                // 以该dataContext为数据源的控件
                if (keyVal.Value == dataContext)
                {
                    var currentControl = keyVal.Key;
                    DependencyProperty dp = getDP(currentControl);

                    String name = currentControl.GetBindingExpression(dp).ParentBinding.Path.Path;
                    Object currentVal = currentVal = currentControl.GetValue(dp);
                    Object originVal = currentControl.GetValue(OriginValProperty);

                    if (context.ContainsKey(name))
                    {
                        context.Remove(name);
                    }
                    context.Add(name, Tuple.Create<Object, Object>(currentControl.GetValue(OriginValProperty), currentVal));
                }
            }

            return context;
        }

        private static DependencyProperty getDP(Control currentControl)
        {
            DependencyProperty dp = null;
            if (currentControl is TextBox)
            {
                dp = TextBox.TextProperty;

            }
            else if (currentControl is ComboBox)
            {
                dp = ComboBox.SelectedValueProperty;
            }

            return dp;
        }
    }
}
